:- import "%%/meta/accessor.sl" #
:- import "%%/lib/dynamic.sl" #
:- import "%%/lib/list.sl" #


-- One-character Read-ahead Lexical Analyzer --

:- structure.define (SCANNER
	twoCharSymbols:list -- Double-character operators
	lineCommenter -- Should be valid tokens
	openCommenter
	closeCommenter

	inStream
	currentLine:global
	token:global
	char:global
) #

scan.readFrom .sc .stream :-
	scanner.inStream .sc .stream,
	scanner.currentLineBecome .sc 1,
	nextCharacter .sc, -- Buffer first character and token
	scan.next .sc #

scan.EOF .sc :- not (peekCharacter .sc .) # -- If even nothing buffered

scan.peek .sc .token :- scanner.token .sc .token #

scan.match .sc .token :-
	scanner.token .sc .token,
	scan.next .sc #

scan.next .sc :-
	whites .sc,
	readToken .sc .token,
	select (scanner.lineCommenter .sc .token =>
		comment.tillEOL .sc, scan.next .sc
	) (scanner.openCommenter .sc .token =>
		comment.tillClose .sc, scan.next .sc
	) (
		scanner.tokenBecome .sc .token
	);
	scanner.tokenUnset .sc ! # -- Met end of file

scan.getCurrentLine .sc .line :- scanner.currentLine .sc .line #


-- Tokenizing --

readToken .sc (ID  .s) :- id .sc .s #
readToken .sc (NUM .n) :- number .sc .s, let .n .s #
readToken .sc (QUO .s) :- string .sc "'" .s #
readToken .sc (STR .s) :- ascii .dQuote 34, string .sc .dQuote .s #
readToken .sc (SYM .s) :- symbol .sc .s # -- Must be last

string .sc .quote .string :-
	matchCharacter .sc .quote,
	stringRemains .sc .quote .string #

stringRemains .sc .quote .ret :-
	matchCharacter .sc .quote,
	select (peekCharacter .sc .quote => -- 'don''t' means don't
		nextCharacter .sc, .ret = .quote
	) (
		.ret = ""
	) ! #
stringRemains .sc .quote .s :-
	matchCharacter .sc .ch,
	stringRemains .sc .quote .remains,
	concat .ch .remains .s #

id .sc .id :-
	alphabet .sc .ch,
	idRemains .sc .remains,
	concat .ch .remains .id ! #

idRemains .sc .s :-
	(alphabet .sc .ch; digit .sc .ch; alphaSymbol .sc .ch),
	idRemains .sc .remains,
	concat .ch .remains .s #
idRemains . '' #

number .sc .number :-
	digit .sc .ch,
	numberRemains .sc .remains,
	concat .ch .remains .number ! #

numberRemains .sc .s :-
	digit .sc .ch,
	numberRemains .sc .remains,
	concat .ch .remains .s #
numberRemains . '' #

symbol .sc .symbol :-
	matchCharacter .sc .s1, -- Anything other than the aboves
	peekCharacter .sc .s2, -- Check whether a double character symbol
	concat .s1 .s2 .s,
	select (scanner.twoCharSymbol .sc .s =>
		.symbol = .s, nextCharacter .sc
	) (
		.symbol = .s1
	) #


-- Characters Identification --

whites .sc :-
	peekCharacter .sc .ch,
	ascii .ch .v, (.v = 9; .v = 10; .v = 13; .v = 32; .v = 255),
	nextCharacter .sc,
	whites .sc ! #
whites . #

alphabet .sc .ch :-
	peekCharacter .sc .ch, (
		.ch = ".";
		asciiGE .ch "A", asciiGE "Z" .ch;
		asciiGE .ch "a", asciiGE "z" .ch
	),
	nextCharacter .sc ! #

alphaSymbol .sc "$" :-
	matchCharacter .sc "$" ! #

digit .sc .ch :-
	peekCharacter .sc .ch,
	asciiGE .ch "0", asciiGE "9" .ch,
	nextCharacter .sc ! #

asciiGE .c1 .c2 :- not (compare .c1 .c2) #


-- Comments --

comment.tillEOL .sc :-
	matchCharacter .sc .ch,
	ascii .ch .v, (
		.v = 10; .v = 13; comment.tillEOL .sc
	) ! #

comment.tillClose .sc :-
	readToken .sc .token, (
		scanner.closeCommenter .sc .token, scan.next .sc;
		comment.tillClose .sc
	) ! #


-- Inputters --

peekCharacter .sc .ch :- scanner.char .sc .ch #

matchCharacter .sc .ch :- scanner.char .sc .ch, nextCharacter .sc #

nextCharacter .sc :-
	scanner.inStream .sc .stream,
	stream.get .stream .ch,
--	write .ch,
	scanner.charBecome .sc .ch,
	if (ascii .ch 10) then (incLineNumber .sc);
	scanner.charUnset .sc -- Met end of file
	! #

incLineNumber .sc :-
	scanner.currentLine .sc .line,
	let .line1 (.line + 1),
	scanner.currentLineBecome .sc .line1 #
